/*
 * Copyright (C) 2008
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.sca4idea.psi;

import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;

import java.util.List;
import java.util.ArrayList;

import com.googlecode.sca4idea.util.SCAAnnotationUtil;

/**
 * PSI Element for working with components that are backed by a java implementation.
 *
 * @author paul.jones
 */
public class SCAJavaComponentTagPsiElement extends SCAComponentTagPsiElement {
    private final PsiClass javaClass;

    public SCAJavaComponentTagPsiElement(XmlTag underlying, XmlTag implTag) {
        super(underlying);

        // Get our module
        Module currentModule = ModuleUtil.findModuleForPsiElement(this);
        
        // Find and resolve our class name
        String javaClassName = implTag.getAttributeValue("class");
        if (currentModule != null && javaClassName != null) {
            this.javaClass = JavaPsiFacade.getInstance(implTag.getProject()).findClass(
                    javaClassName, currentModule.getModuleWithDependenciesAndLibrariesScope(true));
        } else {
            this.javaClass = null;
        }
    }

    public PsiElement resolveServiceInterface(String serviceInterfaceName) {
        if (StringUtil.isEmpty(serviceInterfaceName)) {
            return null;
        }

        if (this.javaClass == null) {
            return null;
        }

        for(PsiClass implemented : this.javaClass.getInterfaces()) {
            if (serviceInterfaceName != null && implemented.getName() != null &&
                    implemented.getName().equals(serviceInterfaceName)) {
                return implemented;
            }
        }

        return null;
    }

    public Object[] getVariantsOfServiceInterfaces() {
        if (this.javaClass == null) {
            return new String[0];
        }

        String componentName = getComponentName();
        List<String> result = new ArrayList<String>();
        for(PsiClass implemented : this.javaClass.getInterfaces()) {
            result.add(componentName + "/" + implemented.getName());
        }

        return result.toArray();
    }

    public PsiElement resolveReference(String referenceName) {
        return resolveAnnotation(referenceName, SCAAnnotationUtil.REFERENCE_ANNOTATION_CLASS);
    }

    @Override
    public Object[] getVariantsOfReferences(boolean qualified) {
        return getVariantsForAnnotation(qualified, SCAAnnotationUtil.REFERENCE_ANNOTATION_CLASS);
    }

    @Override
    public Object[] getVariantsOfProperties(boolean qualified) {
        return getVariantsForAnnotation(qualified, SCAAnnotationUtil.PROPERTY_ANNOTATION_CLASS);
    }

    @Override
    public PsiElement resolveProperty(String propertyName) {
        return resolveAnnotation(propertyName, SCAAnnotationUtil.PROPERTY_ANNOTATION_CLASS);
    }

    public PsiElement getTarget() {
        return javaClass;
    }

    private Object[] getVariantsForAnnotation(boolean qualified, String annotation) {
        if (this.javaClass == null) {
            return new String[0];
        }

        String componentName = getComponentName();
        List<String> result = new ArrayList<String>();
        for (PsiField f : javaClass.getAllFields()) {
            if (SCAAnnotationUtil.getAnnotation(f, annotation) != null) {
                if (qualified) {
                    result.add(componentName + "/" + f.getName());
                } else {
                    result.add(f.getName());
                }
            }
        }
        for (PsiMethod m : javaClass.getAllMethods()) {
            PsiAnnotation refA = m.getModifierList().findAnnotation(annotation);
            if (refA != null) {
                String name = getReferenceNameForSetter(m.getName());

                if (name != null) {
                    if (qualified) {
                        result.add(componentName + "/" + getReferenceNameForSetter(m.getName()));
                    } else {
                        result.add(getReferenceNameForSetter(m.getName()));
                    }
                }
            }
        }

         return result.toArray();
    }

    private PsiElement resolveAnnotation(String name, String annotation) {
        if (this.javaClass == null) {
            return null;
        }

        for (PsiField f : javaClass.getAllFields()) {
            String fieldName = f.getName();

            if (fieldName != null && fieldName.equals(name)) {
                PsiAnnotation refA = f.getModifierList().findAnnotation(annotation);
                if (refA != null) {
                    return f;
                }
            }
        }

        String setterName = getSetterForReference(name);
        if (setterName == null) {
            return null;
        }
        for (PsiMethod m : javaClass.getAllMethods()) {
            if (m.getName().equals(setterName)) {
                PsiAnnotation refA = m.getModifierList().findAnnotation(annotation);
                if (refA != null) {
                    return m;
                }
            }
        }

        return null;
    }

    private static String getSetterForReference(String reference) {
        if (reference.length() < 1) {
            return null;
        }

        String fixedCase = Character.toUpperCase(reference.charAt(0)) + reference.substring(1);
        return "set" + fixedCase;
    }

    private static String getReferenceNameForSetter(String setter) {
        if (!setter.startsWith("set") || setter.length() < 4) {
            return null;
        }

        return Character.toLowerCase(setter.charAt(3)) + setter.substring(4);
    }
}
